/**
 * Copyright 2008 Mathias Doenitz, http://lis.to/
 *
 * This file is part of the lis.to java desktop client. The lis.to java desktop client is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * The lis.to java desktop client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with the lis.to java desktop client.
 * If not, see http://www.gnu.org/licenses/
 */

package listo.client;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import listo.client.model.Folder;
import listo.utils.FileUtils2;
import listo.utils.MiscUtils;
import listo.utils.logging.Log;
import listo.utils.reflection.Reflector;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.util.Properties;

@SuppressWarnings({"UnusedDeclaration"})
@Singleton
public class Preferences {

    private static final String HOME_DIR = "${HOME}";

    public enum TrayIconAction {
        ShowHide, NewTask
    }

    private final String filename;
    private final String defaultPrefText;
    private final Log log;
    private final Reflector reflector;

    private String dataFile;
    private String logFile;
    private String stateFile;
    private String lockFile;
    private String language;
    private int minimumCompletionWordLength;
    private int dateCompletionOptionCount;
    private int timeCompletionMinuteStep;
    private int[] reminderOptions;
    private String defaultFolder;
    private String openWindowHotKey;
    private String newTaskHotKey;
    private String newFolderHotKey;
    private int defaultSnoozeTime;
    private int enabledDelay;
    private boolean useTrayIcon;
    private TrayIconAction trayIconDoubleClick;
    private boolean startMinimized;
    private int autoRemoveCompletedDays;
    private int autoRemoveDeletedDays;
    private boolean strikeOutCompletedTasks;

    @Inject
    public Preferences(Log log, Reflector reflector,
                       @Named("PREFS_FILE")String filename,
                       @Named("DEFAULT_PREFS_RESOURCE")String defaultFileResource) {
        this.log = log;
        this.reflector = reflector;
        this.filename = StringUtils.contains(filename, HOME_DIR) ? filename.replace(HOME_DIR, getHomeDirectory()) :
                filename;

        log.info("Loading default preferences from resource %s", defaultFileResource);
        Properties defaultPreferences;
        try {
            defaultPrefText = FileUtils2.readAllTextFromResource(defaultFileResource);
            defaultPreferences = MiscUtils.deserialize(defaultPrefText);
        } catch (Exception e) {
            throw new RuntimeException("Could not load default preferences: " + e.getMessage());
        }

        boolean save;
        Properties properties;
        File file = new File(this.filename);
        if (file.exists()) {
            log.debug("Loading preferences from file %s", this.filename);
            String text = FileUtils2.readAllText(file);
            properties = MiscUtils.deserialize(text, defaultPreferences);
            save = properties.size() != defaultPreferences.size();
        } else {
            log.info("Preference file not found, using default properties");
            properties = defaultPreferences;
            save = true;
        }

        try {
            reflector.f(this).loadFromProperties(properties);
        }
        catch (Exception e) {
            throw new RuntimeException("Preference file contains errors: " + e.getMessage());
        }

        if (save) save();

        verify();
    }

    /**
     * Saves the preferences to a text file. The default preference file from the
     * resources serves as a template, i.e. the prefs are written "over" the lines
     * from the default preference file so as to keep all formatting (comments, etc.)
     * alive.
     */
    private void save() {
        log.info("Saving preferences to file %s", filename);
        File file = new File(filename);
        File parentDir = file.getParentFile();
        if (parentDir != null && !parentDir.exists()) {
            try {
                FileUtils.forceMkdir(parentDir);
            } catch (IOException e) {
                throw new RuntimeException("Could not create preference file directory: " + e.getMessage());
            }
        }
        Properties properties = reflector.f(this).saveToProperties(false);
        String text = MiscUtils.serialize(properties, "");
        String[] lines = StringUtils.split(text, '\n');

        StringBuilder sb = new StringBuilder();
        String[] defaultPrefTextLines = StringUtils.split(defaultPrefText, '\n');

        for (String defaultLine : defaultPrefTextLines) {
            if (defaultLine.charAt(0) != '#') {
                String defaultLineStripped = StringUtils.deleteWhitespace(defaultLine);
                for (String line : lines) {
                    String[] parts = StringUtils.split(line, "=", 2);
                    if (parts.length == 2 && defaultLineStripped.startsWith(parts[0] + '=')) {
                        defaultLine = StringUtils.join(parts, " = ");
                        break;
                    }
                }
            }
            sb.append(defaultLine).append('\n');
        }

        FileUtils2.writeAllText(sb.toString(), filename);
    }

    private String homeDirectory;

    private String getHomeDirectory() {
        if (StringUtils.isEmpty(homeDirectory)) {
            try {
                homeDirectory = FileSystemView.getFileSystemView().getDefaultDirectory().getCanonicalPath();
            }
            catch (Exception e) {
                throw new RuntimeException("Could not get home directory: " + e.getMessage());
            }
        }
        return homeDirectory;
    }

    private void verify() {
        String problems = "";

        if (StringUtils.isEmpty(dataFile)) problems += "- dataFile must not be empty\n";
        if (StringUtils.isEmpty(lockFile)) problems += "- lockFile must not be empty\n";
        if (StringUtils.isEmpty(stateFile)) problems += "- stateFile must not be empty\n";

        if (StringUtils.isEmpty(language) ||
                (!"system".equals(language) && StringUtils.split(language, '-').length != 2)) {
            problems += "- illegal language setting\n";
        }

        if (dateCompletionOptionCount < 1) problems += "- dateCompletionOptionCount must be > 0\n";

        if (timeCompletionMinuteStep < 1) problems += "- timeCompletionMinuteStep must be > 0\n";
        if (reminderOptions == null || reminderOptions.length == 0) problems += "- illegal reminderOptions\n";

        if (StringUtils.isNotEmpty(defaultFolder)) {
            for (String folderPart : StringUtils.split(defaultFolder, '/')) {
                if (!Folder.isValidName(folderPart)) {
                    problems += "- illegal defaultFolder name\n";
                    break;
                }
            }
        } else {
            problems += "- defaultFolder name must not be empty\n";
        }
        if (defaultSnoozeTime < 1) problems += "- defaultSnoozeTime must be > 0\n";
        if (enabledDelay < 1) problems += "- enabledDelay must be > 0\n";
        if (autoRemoveCompletedDays < 1) problems += "- autoRemoveCompletedDays must be > 0\n";
        if (autoRemoveDeletedDays < 1) problems += "- autoRemoveDeletedDays must be > 0\n";

        if (getOpenWindowHotKey() == null) problems += "- illegal openWindowHotKey\n";
        if (getNewTaskHotKey() == null) problems += "- illegal newTaskHotKey\n";
        if (getNewFolderHotkey() == null) problems += "- illegal newFolderHotKey\n";

        if (StringUtils.isNotEmpty(problems)) {
            throw new RuntimeException(
                    String.format("Preference file %1$s is not quite right:\n%2$s", filename, problems));
        }
    }

    public String getFilename() {
        return filename;
    }

    public String getDataFile() {
        String filename = StringUtils.contains(dataFile, HOME_DIR) ? dataFile
                .replace(HOME_DIR, getHomeDirectory()) : dataFile;
        return filename.replace('/', File.separatorChar);
    }

    public String getLogFile() {
        return StringUtils.contains(logFile, HOME_DIR) ? logFile.replace(HOME_DIR, getHomeDirectory()) :
                logFile == null ? "" : logFile;
    }

    public String getStateFile() {
        return StringUtils.contains(stateFile, HOME_DIR) ? stateFile.replace(HOME_DIR, getHomeDirectory()) : stateFile;
    }

    public String getLockFile() {
        return StringUtils.contains(lockFile, HOME_DIR) ? lockFile.replace(HOME_DIR, getHomeDirectory()) : lockFile;
    }

    public String getLanguage() {
        return language;
    }

    public int getMinimumCompletionWordLength() {
        return minimumCompletionWordLength;
    }

    public int getDateCompletionOptionCount() {
        return dateCompletionOptionCount;
    }

    public int getTimeCompletionMinuteStep() {
        return timeCompletionMinuteStep;
    }

    public int[] getReminderOptions() {
        return reminderOptions;
    }

    public String getDefaultFolder() {
        return defaultFolder;
    }

    public KeyStroke getOpenWindowHotKey() {
        return getSwingKeyStroke(openWindowHotKey);
    }

    public KeyStroke getNewTaskHotKey() {
        return getSwingKeyStroke(newTaskHotKey);
    }

    public KeyStroke getNewFolderHotkey() {
        return getSwingKeyStroke(newFolderHotKey);
    }

    public static KeyStroke getSwingKeyStroke(String descriptor) {
        String s = StringUtils.deleteWhitespace(descriptor);
        if (StringUtils.isEmpty(s)) return null;

        return KeyStroke.getKeyStroke(s.
                toUpperCase().
                replace("SHIFT", "shift").
                replace("ALT", "alt").
                replace("OPT", "alt").
                replace("CTRL", "ctrl").
                replace("WIN", "meta").
                replace("CMD", "meta").
                replace("META", "meta").
                replace('+', ' '));
    }

    public int getDefaultSnoozeTime() {
        return defaultSnoozeTime;
    }

    public int getEnabledDelay() {
        return enabledDelay;
    }

    public int getAutoRemoveCompletedDays() {
        return autoRemoveCompletedDays;
    }

    public int getAutoRemoveDeletedDays() {
        return autoRemoveDeletedDays;
    }

    public boolean useTrayIcon() {
        return useTrayIcon;
    }

    public TrayIconAction getTrayIconDoubleClick() {
        return trayIconDoubleClick;
    }

    public boolean getStartMinimized() {
        return startMinimized;
    }

    public void setStartMinimized(boolean startMinimized) {
        this.startMinimized = startMinimized;
    }

    public boolean strikeOutCompletedTasks() {
        return strikeOutCompletedTasks;
    }
}
